package co.edu.uniandes.sd.certicamara.mundo;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Date;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import co.edu.uniandes.sd.mundo.SntpUtil;

public class CerticamaraTUXTA 
{
	public final static long TIEMPO_VIDA_CERTIFICADO = 300000;
	public final static String ALGORITMO_ENCRIPCION_ASIMETRICO = "RSA";
	public final static String EXPEDIR_CERTIFICADO = "EXPEDIR_CERTIFICADO";
	
	private ObjectOutputStream salida;
	private ObjectInputStream entrada;
	private ServerSocket servidor;
	private Socket conexion;
	private int contador;
	
	public CerticamaraTUXTA() throws Exception
	{
		contador = 1;
		ejecutarServidor();
	}
	
	public void ejecutarServidor() throws Exception
	   {
	      // configurar servidor para que reciba conexiones; procesar las conexiones
	      try {

	         // Paso 1: crear un objeto ServerSocket.
	         servidor = new ServerSocket( 12345, 100 );

	         while ( true ) {

	            try {
	               esperarConexion(); // Paso 2: esperar una conexión.
	               obtenerFlujos();        // Paso 3: obtener flujos de entrada y salida.
	               procesarConexion(); // Paso 4: procesar la conexión.
	            }

	            // procesar excepción EOFException cuando el cliente cierre la conexión 
	            catch ( EOFException excepcionEOF ) {
	               System.err.println( "El servidor terminó la conexión" );
	            }

	            finally {
	               cerrarConexion();   // Paso 5: cerrar la conexión.
	               ++contador;
	            }

	         } // fin de instrucción while

	      } // fin del bloque try

	      // procesar problemas con E/S
	      catch ( IOException excepcionES ) {
	         excepcionES.printStackTrace();
	      }

	   } // fin del método ejecutarServidor

	   // esperar que la conexión llegue, después mostrar información de la conexión
	   private void esperarConexion() throws IOException
	   {
	      mostrarMensaje( "Esperando una conexión\n" );
	      conexion = servidor.accept(); // permitir al servidor aceptar la conexión            
	      mostrarMensaje( "Conexión " + contador + " recibida de: " +
	         conexion.getInetAddress().getHostName() );
	   }

	   // obtener flujos para enviar y recibir datos
	   private void obtenerFlujos() throws IOException
	   {
	      // establecer flujo de salida para los objetos
	      salida = new ObjectOutputStream( conexion.getOutputStream() );
	      salida.flush(); // vaciar búfer de salida para enviar información de encabezado

	      // establecer flujo de entrada para los objetos
	      entrada = new ObjectInputStream( conexion.getInputStream() );

	      mostrarMensaje( "\nSe recibieron los flujos de E/S\n" );
	   }

	   // procesar la conexión con el cliente
	   private void procesarConexion() throws Exception
	   {
	      // enviar mensaje de conexión exitosa al cliente
	      String mensaje = "Conexión exitosa";
	      

	      // procesar los mensajes enviados por el cliente

	         // leer el mensaje y mostrarlo en pantalla
	         try {
	            mensaje = ( String ) entrada.readObject();
	            procesarMensaje(mensaje);
	            mostrarMensaje( "\n" + mensaje );
	         }

	         // atrapar problemas que pueden ocurrir al tratar de leer del cliente
	         catch ( ClassNotFoundException excepcionClaseNoEncontrada ) {
	            mostrarMensaje( "\nSe recibió un tipo de objeto desconocido" );
	         }

	   } // fin del método procesarConexion

	   private void procesarMensaje(String mensaje) throws Exception 
	   {
			if(mensaje.startsWith(EXPEDIR_CERTIFICADO))
			{
				String login = mensaje.split(";")[1];
				String pass = mensaje.split(";")[2];
				PrivateKey privateKey = (PrivateKey) entrada.readObject();
				PublicKey publicKey = (PublicKey) entrada.readObject();
				enviarDatos(expedirCertificadoDigital(login, pass, privateKey, publicKey));
			}
		   
	   }
	   
	   public X509Certificate expedirCertificadoDigital(String login, String pass, PrivateKey llavePrivada, PublicKey llavePublica)throws Exception{
			ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withRSAEncryption").build(llavePrivada);
	        Date actual = SntpUtil.darHoraOficial();
			X500Principal issuer = new X500Principal("CN=Tuxta Leaks"+login+pass);
			BigInteger serial = BigInteger.valueOf(1);
			Date notBefore = actual;
			Date notAfter = new Date(actual.getTime()+TIEMPO_VIDA_CERTIFICADO);
			X500Principal subject = new X500Principal("CN=Tuxta Leaks"+login+pass);
			X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, llavePublica);
			return new JcaX509CertificateConverter().getCertificate(certBuilder.build(sigGen));
		}

	// cerrar flujos y socket
	   private void cerrarConexion() 
	   {
	      mostrarMensaje( "\nFinalizando la conexión\n" );
	      
	      try {
	         salida.close();
	         entrada.close();
	         conexion.close();
	      }
	      catch( IOException excepcionES ) {
	         excepcionES.printStackTrace();
	      }
	   }

	   // enviar mensaje al cliente
	   private void enviarDatos( X509Certificate cert )
	   {
	      // enviar objeto al cliente
	      try {
	         salida.writeObject( cert );
	         salida.flush();
	         mostrarMensaje( "\nSERVIDOR>>> "  );
	      }

	      // procesar problemas que pueden ocurrir al enviar el objeto
	      catch ( IOException excepcionES ) {
	         
	      }
	   }
	   
	   private void mostrarMensaje(String str)
	   {
		   System.out.println(str);
	   }
	   
	   public static void main (String ... args)
	   {
		   try {
			CerticamaraTUXTA c = new CerticamaraTUXTA();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	   }

}
